# -*- coding: utf-8 -*-

from common import trace_method, logable, NDW_DEBUG, NDW_ERROR

__author__ = 'totem3006'


#cf_cmd_fmt = """clang -cc1 -analyze -analyzer-checker debug.DumpCFG %(source-file)s"""

STD_INCLUDES = ["-internal-isystem", "/usr/local/include",
                "-internal-isystem", "/usr/bin/../lib/clang/3.5/include",
                "-internal-externc-isystem", "/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/include",
                "-internal-externc-isystem", "/usr/include/x86_64-linux-gnu",
                "-internal-externc-isystem", "/include",
                "-internal-externc-isystem", "/usr/include"]

@logable
class ControlFlowAnalyzer(object):
    def __init__(self):
        self.analyzer_dump = []
        self.source_file_exitst = False
        self.state = CBParseState()

    @trace_method
    def run_analyzer(self, source_file):
        import subprocess, os

        if os.path.exists(source_file):
            self.source_file_exitst = True

            exec_commnd = ["clang", "-cc1"] + STD_INCLUDES + [ "-analyze", "-analyzer-checker", "debug.DumpCFG", source_file]
            print " ".join(exec_commnd)
            proc = subprocess.Popen(exec_commnd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

            ff_list = []
            ff = FunctionFlow()
            cb = ControlBlock()
            for ln in iter(proc.stdout.readline, ""):
                line = ln.strip()
                self.set_state(line)

                if self.state.state == self.state.END:
                    ff.add_cb(cb)
                    if cb.name == "B0":
                        ff_list.append(ff)
                        ff = FunctionFlow()
                    cb = ControlBlock()
                elif self.state.state == self.state.BNAME:
                    cb = ControlBlock()
                    cb.set_name(line)
                elif self.state.state == self.state.STUB:
                    cb.add_line(line)
                elif self.state.state == self.state.PREDS:
                    cb.add_preds(line)
                elif self.state.state == self.state.SUCCS:
                    cb.add_succs(line)
                elif self.state.state == self.state.FNAME:
                    ff.set_name(line)

            self.analyzer_dump = ff_list
        else:
            self.log(NDW_ERROR, "file '%s' doesn't exists", source_file)

    @trace_method
    def render_images(self, image_dir_path):
        import os, hashlib
        def get_fname(s):
            print s.split()

        md5chiper = hashlib.md5()
        get_func_name = lambda s: s.split("(", 1)[0].split(" ")[-1]
        for func_flow in self.analyzer_dump:
            md5chiper.update(get_func_name(func_flow.function_name))
            image_fname = md5chiper.hexdigest()

            full_image_name = os.path.join(image_dir_path, image_fname + ".svg")
            try:
                func_flow.render_image(full_image_name)
            except Exception, e:
                print full_image_name
            func_flow.saved_image_file = os.path.join("img", image_fname + ".svg")

    @trace_method
    def set_state(self, s):
        self.log(NDW_DEBUG, "before state = %s", self.state)
        # s = line.strip()
        if self.state.state == self.state.BEGIN:
            if s.startswith("["):
                self.state.state = self.state.BNAME
            else:
                self.state.state = self.state.FNAME
        elif self.state.state == self.state.BNAME or self.state.state == self.state.STUB:
            if s.startswith("Preds"):
                self.state.state = self.state.PREDS
            elif s.startswith("Succs"):
                self.state.state = self.state.SUCCS
            elif not s or s.isspace():
                self.state.state = self.state.END
            else:
                self.state.state = self.state.STUB
        elif self.state.state == self.state.PREDS:
            if s.startswith("Succs"):
                self.state.state = self.state.SUCCS
            elif not s or s.isspace():
                self.state.state = self.state.END
            else:
                self.state.state = self.state.STUB
        elif self.state.state == self.state.SUCCS:
            if not s or s.isspace():
                self.state.state = self.state.END
            else:
                self.state.state = self.state.STUB
        elif self.state.state == self.state.END:
            if s.startswith("["):
                self.state.state = self.state.BNAME
            else:
                self.state.state = self.state.FNAME
        elif self.state.state == self.state.FNAME:
            self.state.state = self.state.BNAME
        else:
            self.state.state = self.state.STUB

        self.log(NDW_DEBUG, "state after = %s", self.state)

class FunctionFlow(object):
    def __init__(self):
        self.function_name = ""
        self.control_block_list = []

    def add_cb(self, cb):
        self.control_block_list.append(cb)

    def set_name(self, name):
        self.function_name = name

    def render_image(self, image_file_name=None):
        import igraph

        g = igraph.Graph(directed=True)
        for cb in self.control_block_list:
            clr = "gray"
            if cb.last:
                clr = "red"
            elif cb.first:
                clr = "green"
            g.add_vertex(cb.name, label=cb.name, color=clr, size=30)

        for cb in self.control_block_list:
            for it in cb.succs:
                if it != "NULL":
                    g.add_edge(cb.name, it)
        layout = g.layout("fr")
        igraph.plot(g, image_file_name, layout=layout)


class ControlBlock(object):
    def __init__(self):
        self.first = False
        self.last = False
        self.name = ""
        self.lines = []
        self.preds = []
        self.succs = []

    def __str__(self):
        return "\n".join([self.name, "\n".join(self.lines), "preds=", ", ".join(self.preds), "succs=", ", ".join(self.succs)])
    def set_name(self, name):
        import re
        s = re.search("B\d+", name)
        self.name = s.group()
        if self.name == "B0":
            self.last = True

        if "EXIT" in name:
            self.last = True
        elif "ENTRY" in name:
            self.first = True

    def add_line(self, line_str):
        self.lines.append(line_str)

    def add_preds(self, pred):
        ignore, preds = pred.split(": ")
        self.preds.extend(preds.split())
        self.preds = filter(lambda x: x != 'NULL', self.preds)

    def add_succs(self, succ):
        ignore, succs = succ.split(": ")
        self.succs.extend(succs.split())
        self.succs = filter(lambda x: x != 'NULL', self.succs)

    def to_html(self):
        pass

class CBParseState(object):
    BEGIN = 0
    BNAME = 1
    LINES = 2
    PREDS = 3
    PREDS = 3
    SUCCS = 4
    END = 5
    STUB = 6
    FNAME = 7

    def __init__(self):
        self.state = self.BEGIN

    def __str__(self):
        if self.state == self.BEGIN:
            result = "BEGIN"
        elif self.state == self.BNAME:
            result = "BNAME"
        elif self.state == self.LINES:
            result = "LINES"
        elif self.state == self.PREDS:
            result = "PREDS"
        elif self.state == self.SUCCS:
            result = "SUCCS"
        elif self.state == self.END:
            result = "END"
        elif self.state == self.FNAME:
            result = "FNAME"
        else:
            result = "STUB"

        return result

    def __repr__(self):
        return "<%s: %d>" % (str(self), self.state)
